package com.itcv.spring.security.demo;

import com.itcv.spring.security.demo.authentication.CustomLogoutSuccessHandler;
import com.itcv.spring.security.demo.authentication.CustomSavedRequestAwareAuthenticationSuccessHandler;
import com.itcv.spring.security.demo.authentication.CustomSimpleUrlAuthenticationFailureHandler;
import com.itcv.spring.security.demo.authentication.jdbc.CustomJdbcUserDetailsService;
import com.itcv.spring.security.demo.authentication.manager.RoleAccessDecisionManager;
import com.itcv.spring.security.demo.dao.SysFuncDao;
import com.itcv.spring.security.demo.dao.SysRoleDao;
import com.itcv.spring.security.demo.intercept.CustomFilterSecurityInterceptor;
import com.itcv.spring.security.demo.result.SysFuncRole;
import com.itcv.spring.security.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.access.vote.AffirmativeBased;
import org.springframework.security.access.vote.RoleVoter;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configuration.EnableGlobalAuthentication;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.stream.Collectors;


//@EnableGlobalMethodSecurity(prePostEnabled = true) //开启权限认证注解
@EnableGlobalAuthentication
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    /**
     * springSecurity学习
     * 1.自定义登录成功执行方法
     * 2.自定义退出登录执行方法
     * 3.自定义登录失败执行方法
     */
@Autowired
private UserService userService;
@Autowired
private SysRoleDao sysRoleDao;
@Autowired
private SysFuncDao funcDao;
@Autowired
private RoleAccessDecisionManager roleAccessDecisionManager;

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/css/**", "/js/**", "/plugins/**", "/images/**", "/fonts/**");
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .formLogin()
                .loginPage("/login").defaultSuccessUrl("/hello").successHandler(customAuthenticationSuccessHandler())
                .permitAll() //允许前边的配置执行
                .failureHandler(customSimpleUrlAuthenticationFailureHandler()).permitAll()//登录失败自定义处理方式
                .and()
                .logout().logoutSuccessHandler(customLogoutSuccessHandler())//指定登出成功跳转页面
                .permitAll()
                .and()
                .authorizeRequests()
                .antMatchers(("/home")).authenticated() //加权限验证//antMatchers 顺序有优先级，后边的会覆盖前边的
                .antMatchers("/login_fail").permitAll()
                .antMatchers("/", "/home").permitAll()
                .anyRequest().authenticated();

                http.addFilterAfter(customFilterSecurityInterceptor(), FilterSecurityInterceptor.class);
           /* .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {

                @Override
                public <O extends FilterSecurityInterceptor> O postProcess(O o) {
                    o.setSecurityMetadataSource(new DefaultFilterInvocationSecurityMetadataSource(obtainRequestMap()));
                    o.setAccessDecisionManager(roleAccessDecisionManager);
                    return o;
                }
            });*/


       // http.authorizeRequests().antMatchers().permitAll().anyRequest().authenticated().and();

        /**
         * 添加菜单权限验证
         */
        //http.addFilterBefore(customFilterSecurityInterceptor(), FilterSecurityInterceptor.class);


    }

/*    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        UserDetails user = //User.builder().username("user").password("password").roles("USER").build();
                User.withDefaultPasswordEncoder()
                        .username("user")
                        .password("password")
                        .roles("Manager")
                        .build();

        return new InMemoryUserDetailsManager(user);
    }*/

    /**
     * 自定义登录成功之后操作
     * @return
     */
    @Bean
    public AuthenticationSuccessHandler customAuthenticationSuccessHandler() {
        CustomSavedRequestAwareAuthenticationSuccessHandler customSavedRequestAwareAuthenticationSuccessHandler = new CustomSavedRequestAwareAuthenticationSuccessHandler();
        customSavedRequestAwareAuthenticationSuccessHandler.setDefaultTargetUrl("/hello");
        return customSavedRequestAwareAuthenticationSuccessHandler;
    }

    /**
     * 自定义登出之后操作
     * @return
     */
    @Bean
    public LogoutSuccessHandler customLogoutSuccessHandler() {
        CustomLogoutSuccessHandler customLogoutSuccessHandler = new CustomLogoutSuccessHandler();
        customLogoutSuccessHandler.setDefaultTargetUrl("/logout_success");
        return customLogoutSuccessHandler;
    }

    /**
     * 自定义登录失败操作
     * @return
     */
    @Bean
    public AuthenticationFailureHandler customSimpleUrlAuthenticationFailureHandler() {
        CustomSimpleUrlAuthenticationFailureHandler customSimpleUrlAuthenticationFailureHandler = new CustomSimpleUrlAuthenticationFailureHandler();
        customSimpleUrlAuthenticationFailureHandler.setDefaultFailureUrl("/login_fail");
        return customSimpleUrlAuthenticationFailureHandler;
    }

    /**
     * 读取数据库中的用户信息
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(customJdbcUserDetailsService()).passwordEncoder(new BCryptPasswordEncoder());
    }

    private UserDetailsService customJdbcUserDetailsService() {
        CustomJdbcUserDetailsService userDetailsService = new CustomJdbcUserDetailsService();
        userDetailsService.setUserService(userService);
        userDetailsService.setRoleDao(sysRoleDao);
        return userDetailsService;
    }

    /**
     * 菜单权限功能相关
     * @return
     * @throws Exception
     */
    private FilterSecurityInterceptor customFilterSecurityInterceptor() throws Exception {
        CustomFilterSecurityInterceptor filterSecurityInterceptor = new CustomFilterSecurityInterceptor();
        filterSecurityInterceptor.setSecurityMetadataSource(new DefaultFilterInvocationSecurityMetadataSource(obtainRequestMap()));
       // filterSecurityInterceptor.setAccessDecisionManager(roleAccessDecisionManager);
        filterSecurityInterceptor.setAccessDecisionManager(accessDecisionManager());
        filterSecurityInterceptor.setAuthenticationManager(authenticationManager());

        // filterSecurityInterceptor.setObserveOncePerRequest(false);
        return filterSecurityInterceptor;
    }

    private AccessDecisionManager accessDecisionManager() {
        System.out.println("我什么时候执行一下");
        List<AccessDecisionVoter<? extends Object>> voters = new ArrayList<>();
        voters.add(new RoleVoter());
        return new AffirmativeBased(voters);
    }

    private LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> obtainRequestMap() {
        List<SysFuncRole> sysFuncRoles = this.funcDao.listFuncRole();
        if (CollectionUtils.isEmpty(sysFuncRoles)) {
            return new LinkedHashMap<>();
        }

        Map<String, Set<String>> urlRoleMap = new HashMap<>();

        for (SysFuncRole sysFuncRole : sysFuncRoles) {
            String url = determineAntUrl(sysFuncRole.getUrl());

            Set<String> configAttributes = urlRoleMap.get(url);

            if (configAttributes == null) {
                configAttributes = new HashSet<>();
            }

            configAttributes.add(sysFuncRole.getRoleCode());
            urlRoleMap.put(url, configAttributes);
        }

        LinkedHashMap<RequestMatcher, Collection<ConfigAttribute>> requestMap = new LinkedHashMap<>();

        for(String url : urlRoleMap.keySet()) {
            Set<String> needRoles = urlRoleMap.get(url);

            // 注意此处，我们设置ConfigAttribute为 ROLE_ 前缀加上角色标识，与 CustomJdbcUserDetailsService 里面组织UserDetails设置角色标识呼应
            requestMap.put(new AntPathRequestMatcher(url), needRoles.stream().map(role -> new SecurityConfig("ROLE_" + role)).collect(Collectors.toSet()));
        }

        return requestMap;
    }

    /**
     * 去掉最后一个 <code>/</code> 后的内容，以 <code>**</code> 代替，以匹配 <code>ant</code> 风格。
     * @param url 功能地址
     * @return ant风格地址
     */
    private String determineAntUrl(String url) {
        if (StringUtils.isEmpty(url)) {
            return null;
        }

        return url.substring(0, url.lastIndexOf("/")) + "/**";
    }
}
